package org.finesys.common.es.util;

import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.finesys.common.es.properties.MyElasticsearchProperties;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * ESUtil类提供了与Elasticsearch交互的工具方法。
 */
@Slf4j
public class ESUtil {

    /**
     * 创建并返回一个配置好的RestClientTransport实例。
     *
     * @return 配置好的RestClientTransport实例
     */
    public static RestClientTransport restClientTransport(final MyElasticsearchProperties elasticsearchProperties, final JsonpMapper jacksonJsonpMapper, final StringEncryptor stringEncryptor) {
        //解析密码
        if (elasticsearchProperties.isEnableEncrypt() && !ObjectUtils.isEmpty(elasticsearchProperties.getPassword())) {
            String password = elasticsearchProperties.getPassword();
            if (ObjectUtils.isEmpty(password)) {
                throw new IllegalArgumentException("Elasticsearch password is empty");
            }
            password = decryptPassword(password, stringEncryptor);
            elasticsearchProperties.setPassword(password);
        }
        RestClient restClient = ESUtil.restClient(elasticsearchProperties);
        return new RestClientTransport(restClient, jacksonJsonpMapper);
    }

    /**
     * 解密密码
     *
     * @param password 待解密的密码
     * @return 解密后的原始密码，如果解密失败则抛出RuntimeException
     * @throws RuntimeException 如果解密过程中出现异常
     */
    private static String decryptPassword(String password, StringEncryptor stringEncryptor) {
        try {
            return stringEncryptor.decrypt(password);
        } catch (Exception e) {
            log.error("解密密码失败", e);
            throw new RuntimeException("密码解密失败", e);
        }
    }

    /**
     * 创建并配置RestClient实例
     *
     * @return 配置好的RestClient实例
     */
    public static RestClient restClient(final MyElasticsearchProperties elasticsearchProperties) {
        // 构建RestClient连接对象
        RestClientBuilder restClientBuilder = RestClient.builder(toHttpHost(elasticsearchProperties.getUris()));
        // 设置用于认证的用户名和密码
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(elasticsearchProperties.getUsername(), elasticsearchProperties.getPassword()));
        // 配置连接请求的超时参数
        restClientBuilder.setRequestConfigCallback(request -> {
            request.setConnectionRequestTimeout((int) elasticsearchProperties.getConnectionTimeout().getSeconds());
            request.setConnectTimeout((int) elasticsearchProperties.getConnectionTimeout().getSeconds());
            request.setSocketTimeout((int) elasticsearchProperties.getSocketTimeout().getSeconds());
            return request;
        });
        // 配置HTTP客户端的连接数
        restClientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder -> {
            httpAsyncClientBuilder.setMaxConnTotal(elasticsearchProperties.getMaxConnTotal());
            httpAsyncClientBuilder.setMaxConnPerRoute(elasticsearchProperties.getMaxConnPerRoute());
            httpAsyncClientBuilder.setConnectionTimeToLive(elasticsearchProperties.getConnTimeToLive(),elasticsearchProperties.getConnTimeToLiveTimeUnit());
            httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            return httpAsyncClientBuilder;
        });
        // 构建并返回配置好的RestClient实例
        return restClientBuilder.build();
    }

    /**
     * 将主机列表转换为HttpHost数组。
     *
     * @param hostList 主机列表，包含主机地址和端口号的字符串列表。
     * @return 转换后的HttpHost数组，每个元素对应一个HttpHost对象。
     *
     * <p>此方法首先检查传入的hostList是否为空，如果为空，则默认创建一个指向"localhost"和端口9200的HttpHost对象数组。
     * 如果hostList不为空，则遍历列表中的每个元素，去除元素中的"http://"或"https://"前缀（如果存在），然后将其分割为主机名和端口号，
     * 并根据这些信息创建一个HttpHost对象。最后，将这些HttpHost对象存储在数组中并返回。
     */
    public static HttpHost[] toHttpHost(List<String> hostList) {
        int size = 1;
        if (!ObjectUtils.isEmpty(hostList)) {
            size = hostList.size();
        }
        HttpHost[] httpHosts = new HttpHost[size];
        if (ObjectUtils.isEmpty(hostList)) {
            HttpHost httpHost = new HttpHost("localhost", 9200);
            httpHosts[0] = httpHost;
            return httpHosts;
        }
        for (int i = 0; i < hostList.size(); i++) {
            String host = hostList.get(i);
            //去除http和https前缀,只保留 localhost:9200等样式
            host = host.replaceAll("http://", "").replaceAll("https://", "");
            //
            String[] strings = host.split(":");
            HttpHost httpHost = new HttpHost(strings[0], Integer.valueOf(strings[1]));
            httpHosts[i] = httpHost;
        }
        //获取数组信息
        return httpHosts;
    }
}
